Skip to content

Refuse to spawn an extraction pool that would spawn its own (#1637) - #3620

Open
ayushcodes10 wants to merge 12 commits into
Graphify-Labs:v8from
ayushcodes10:fix-1637-windows-spawn-guard
Open

ayushcodes10 wants to merge 12 commits into
Graphify-Labs:v8from
ayushcodes10:fix-1637-windows-spawn-guard

Conversation

@ayushcodes10

Copy link
Copy Markdown
Contributor

Summary

Fixes #1637. On Windows, a caller script with no if __name__ == "__main__": guard turns extract()'s parallel path into a fork bomb rather than a slow failure: every spawned worker re-executes the top-level module on import, and if that module calls extract() again at module scope (the common shape of a quick inline runner script), the worker opens its own ProcessPoolExecutor, whose own guard-less children do the same. A second reporter measured this directly — ~1,278 python.exe processes and ~28 GB consumed before the machine had to be power-cycled.

Root cause

_extract_parallel already caught BrokenProcessPool and fell back to sequential extraction, but only after the pool tried to run. ProcessPoolExecutor._adjust_process_count() respawns dying workers as they die, and on a guard-less caller every respawned worker dies the same way (re-importing __main__ triggers _check_not_importing_main) and — critically — re-executes the module-level extract() call, opening yet another pool. The pool keeps minting doomed processes faster than any per-future exception can propagate and stop it, so the reactive except BrokenProcessPool handler never gets a chance to run.

Fix

Two checks now run in _extract_parallel before a ProcessPoolExecutor is ever constructed:

  1. Unconditional: refuse to open a pool when already running inside a multiprocessing child (multiprocessing.parent_process() is not None). A legitimate call to _extract_parallel only ever happens in the main process — a worker should only ever run _extract_single_file via the pool machinery — so this is always safe and breaks the specific recursive-spawn mechanism regardless of platform.
  2. Windows only: pre-emptively decline the pool when the caller's own __main__ module's source lacks the __main__ guard text, instead of discovering the failure only after the pool has already started respawning. Scoped to sys.platform == "win32" (matching the existing Windows-specific worker cap already in this function) since macOS/Linux typically default to fork, where this class of guard-less-caller re-execution doesn't occur the same way, and scoping it avoids changing extraction behavior on non-Windows CI.

Both checks fall back to sequential extraction exactly like the existing BrokenProcessPool handler — correct output, just without the pool.

Test plan

  • Added test_extract_parallel_declines_pool_inside_a_spawned_worker, test_extract_parallel_declines_pool_on_windows_when_caller_lacks_guard, and test_extract_parallel_still_spawns_pool_on_windows_when_caller_has_guard to tests/test_extract.py.
  • Confirmed all pre-existing Windows-spawn fallback tests in the same file still pass unmodified.
  • Full suite: python3 -m pytest -q — 5624 passed, 68 skipped, no regressions.
  • python3 -m tools.skillgen --check — OK.

🤖 Generated with Claude Code

ayushcodes10 and others added 3 commits September 17, 2026 01:57
A guard less caller (no if __name__ == "__main__": block) makes every
Windows spawned worker re execute the top level module on import. If
that module calls extract() again at module scope, the worker opens
its own pool, whose own guard less children do the same, faster than
any per future BrokenProcessPool exception can surface and stop it.

Two checks now run before the pool is ever opened: refuse
unconditionally when already inside a multiprocessing child (a
legitimate call only ever happens in the main process), and on
Windows, skip the pool when the caller's own __main__ module has no
guard, rather than only catching the failure after the fact.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Covers both new guards in _extract_parallel: refusing to open a pool
from inside a spawned worker, and pre emptively declining the pool on
Windows for a caller whose main module lacks the guard, while
confirming a properly guarded caller still takes the pool path.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

@graphify-labs graphify-labs Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Graphify reviewed this change.

Worth a look — the grounded gate found no coupling regressions or blocking issues, but 5 advisory finding(s) below merit a look before merge.

Formal verification. No changes could be formally verified in this run.


Graphify review — findings

Guards extract()'s parallel path against a fork bomb on guard-less Windows callers: _extract_parallel now refuses to open a ProcessPoolExecutor whenever multiprocessing.parent_process() shows it's already inside a spawned worker, and on Windows it also declines pre-emptively when the caller's __main__ source lacks an if __name__ == "__main__": guard (detected by _caller_main_lacks_guard, which treats an unreadable source as "can't tell"). Both cases warn and hand the work back for sequential extraction; a properly guarded Windows caller still takes the pool path.

Worth a look

  • Windows fork-bomb guard is bypassed by any __main__ substringgraphify/extract.py:6393 · Escalate · high
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
  • Guard detection treats any 'main' occurrence as a valid multiprocessing guardgraphify/extract.py · Escalate · medium
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
  • Windows guard check is bypassed by any 'main' stringgraphify/extract.py:6388 · Escalate · medium
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
  • Guard detection matches any 'main' substring, not the guardgraphify/extract.py:6391 · Escalate · medium
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
  • Guard detection false-negative for any file containing the string 'main'graphify/extract.py:6392 · Escalate · medium
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
Analysis details — impact, health, verification

Impact & health

Graphify review

Impact — 2474 functions depend on the 875 functions this change touches.

Health — this change adds coupling hotspots:

  • new: extract() — 645 callers, 45 callees
  • new: _rebuild_code() — 129 callers, 54 callees
  • new: extract_js() — 85 callers, 4 callees
  • new: extract_xaml() — 19 callers, 17 callees
  • new: dispatch_command() — 2 callers, 125 callees
  • new: _get_extractor() — 26 callers, 6 callees
  • new: run_pipeline() — 8 callers, 13 callees
  • new: collect_files() — 17 callers, 6 callees
  • …and 30 more — each is listed as a finding

Verification — 2474 functions in the blast radius were not formally verified this run (proofs are advisory here).

Gate & verification

graphify gate

PASS — objectively clean (no health regressions, tests not run — proofs not run this pass (advisory)). Grounded, not self-assessed.

Advisory (not blocking):

  • verification_scope: 2299 function(s) in the blast radius were not formally verified this run

Test selection

Test selection

286 of 286 test file(s) selected (100%) via static blast radius.

Escalated to a full run for safety — the selection is not trustworthy on its own (see below). CI should run the whole suite.

  • tests/test_affected_cli.py — full-run-safety
  • tests/test_affected_member_seed.py — full-run-safety
  • tests/test_agents_platform.py — full-run-safety
  • tests/test_analyze.py — full-run-safety
  • tests/test_anthropic_custom_endpoint.py — full-run-safety
  • tests/test_antigravity_install.py — full-run-safety
  • tests/test_apm_fallback_version.py — full-run-safety
  • tests/test_architecture_doc.py — full-run-safety
  • tests/test_astro_extraction.py — impact, full-run-safety
  • tests/test_astro_import_ids.py — impact, full-run-safety
  • tests/test_atomic_canvas_export.py — full-run-safety
  • tests/test_atomic_version_stamp.py — full-run-safety
  • tests/test_atomic_writes.py — full-run-safety
  • tests/test_backend_env_isolation.py — full-run-safety
  • tests/test_backend_extras.py — full-run-safety
  • tests/test_benchmark.py — full-run-safety
  • tests/test_benchmark_raw_graph.py — full-run-safety
  • tests/test_build.py — impact, full-run-safety
  • tests/test_build_merge_dedup_scope.py — full-run-safety
  • tests/test_build_merge_hyperedges_and_prune.py — full-run-safety
  • tests/test_build_merge_shrink_guard.py — full-run-safety
  • tests/test_builtin_global_type_refs.py — impact, full-run-safety
  • tests/test_cache.py — full-run-safety
  • tests/test_callflow_html.py — full-run-safety
  • tests/test_cargo_introspect.py — full-run-safety
  • tests/test_carried_hyperedge_remap.py — full-run-safety
  • tests/test_case_sensitive_resolution.py — impact, full-run-safety
  • tests/test_charmap_encoding.py — full-run-safety
  • tests/test_chunking.py — full-run-safety
  • tests/test_cjs_module_extension.py — impact, full-run-safety
  • tests/test_claude_cli_backend.py — full-run-safety
  • tests/test_claude_md.py — full-run-safety
  • tests/test_cli_broken_pipe.py — full-run-safety
  • tests/test_cli_export.py — full-run-safety
  • tests/test_cli_help.py — full-run-safety
  • tests/test_cluster.py — full-run-safety
  • tests/test_codebuddy.py — full-run-safety
  • tests/test_community_hub_labels.py — full-run-safety
  • tests/test_community_labels_skill.py — full-run-safety
  • tests/test_confidence.py — full-run-safety
  • tests/test_corrupt_graph_json.py — full-run-safety
  • tests/test_cpp_nested_and_cli.py — impact, full-run-safety
  • tests/test_cpp_objc_cross_file_calls.py — impact, full-run-safety
  • tests/test_cpp_preprocess.py — full-run-safety
  • tests/test_cross_extension_reexport_self_cycle.py — impact, full-run-safety
  • tests/test_cross_language_call_resolution.py — impact, full-run-safety
  • tests/test_cross_repo_external_call_guards.py — impact, full-run-safety
  • tests/test_cross_repo_member_calls.py — impact, full-run-safety
  • tests/test_cross_repo_shared_types.py — full-run-safety
  • tests/test_csharp_call_site_generic_args.py — impact, full-run-safety
  • … and 236 more

non-code file(s) changed (CHANGELOG.md) → running the full suite for safety (a code graph can't see config/fixture/data deps)

changed code file(s) with no mapped test (CHANGELOG.md) — a coverage gap or a missing link — running the full suite rather than only the selected tests

Selection is safe under the controlled-regression assumption; always-run tests + a periodic full run are the backstops. Advisory — it never changes the check verdict.

Formal verification

Could not verify: Could not verify \_extract\_parallel.

The verifier did not have enough to check \_extract\_parallel, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: no capturable inputs from the test suite; property tier: parameter `root` is annotated `Path` — outside the synthesizable primitive/collection set

· 38 more finding(s) on lines outside this diff (see the check run).

ayushcodes10 and others added 3 commits September 17, 2026 20:55
A review on this PR flagged that the guard less caller check used
plain substring containment: any mention of __main__ anywhere in the
caller's source, including a comment, docstring, or unrelated string
literal, made the check report a guard that was not actually there,
defeating the fork bomb protection this function exists to provide.
Now matches the actual if statement (either operand order), so only a
real guard counts.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Covers the false positive a plain reviewer found (an unrelated
mention of __main__ in a comment or docstring must not be read as a
guard) and the reversed operand order, which is valid Python and must
still be recognized as a real guard.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@ayushcodes10

Copy link
Copy Markdown
Contributor Author

Good catch — fixed. The guard check used a bare substring test, so any mention of __main__ anywhere in the caller's source (a comment, docstring, or unrelated string literal) was read as a real guard. It now matches the actual if __name__ == "__main__": statement (either operand order), with regression tests for both the false positive case and the reversed operand order.

@graphify-labs graphify-labs Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Graphify reviewed this change.

Worth a look — the grounded gate found no coupling regressions or blocking issues, but 3 advisory finding(s) below merit a look before merge.

Formal verification. No changes could be formally verified in this run.


Graphify review — findings

Prevents a fork-bomb when extract() runs in parallel from a guard-less Windows caller script: _extract_parallel now refuses to open a ProcessPoolExecutor whenever it detects it is already inside a multiprocessing child (via multiprocessing.parent_process(), on any platform), and on Windows pre-emptively declines with a stderr warning when the __main__ module's source lacks an if __name__ == "__main__": guard, falling back to sequential extraction. The guard detection in _caller_main_lacks_guard matches the actual guard statement with _MAIN_GUARD_RE rather than a substring, so a __main__ mention in a comment, docstring, or string literal isn't mistaken for a real guard, and a source-read failure is treated as "can't tell" rather than "missing". Guarded callers keep the pool path and their parallelism.

Worth a look

  • Guard detector treats triple-quoted string contents as a real main guardgraphify/extract.py:6370 · Escalate · high
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
  • Guard detector treats docstring example as real main guardgraphify/extract.py:6394 · Escalate · high
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
  • Guard detector rejects valid parenthesized main guardgraphify/extract.py:6372 · Escalate · medium
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
Analysis details — impact, health, verification

Impact & health

Graphify review

Impact — 2479 functions depend on the 880 functions this change touches.

Health — this change adds coupling hotspots:

  • new: extract() — 645 callers, 45 callees
  • new: _rebuild_code() — 129 callers, 54 callees
  • new: extract_js() — 85 callers, 4 callees
  • new: extract_xaml() — 19 callers, 17 callees
  • new: dispatch_command() — 2 callers, 125 callees
  • new: _get_extractor() — 26 callers, 6 callees
  • new: run_pipeline() — 8 callers, 13 callees
  • new: collect_files() — 17 callers, 6 callees
  • …and 30 more — each is listed as a finding

Verification — 2479 functions in the blast radius were not formally verified this run (proofs are advisory here).

Gate & verification

graphify gate

PASS — objectively clean (no health regressions, tests not run — proofs not run this pass (advisory)). Grounded, not self-assessed.

Advisory (not blocking):

  • verification_scope: 2304 function(s) in the blast radius were not formally verified this run

Test selection

Test selection

286 of 286 test file(s) selected (100%) via static blast radius.

Escalated to a full run for safety — the selection is not trustworthy on its own (see below). CI should run the whole suite.

  • tests/test_affected_cli.py — full-run-safety
  • tests/test_affected_member_seed.py — full-run-safety
  • tests/test_agents_platform.py — full-run-safety
  • tests/test_analyze.py — full-run-safety
  • tests/test_anthropic_custom_endpoint.py — full-run-safety
  • tests/test_antigravity_install.py — full-run-safety
  • tests/test_apm_fallback_version.py — full-run-safety
  • tests/test_architecture_doc.py — full-run-safety
  • tests/test_astro_extraction.py — impact, full-run-safety
  • tests/test_astro_import_ids.py — impact, full-run-safety
  • tests/test_atomic_canvas_export.py — full-run-safety
  • tests/test_atomic_version_stamp.py — full-run-safety
  • tests/test_atomic_writes.py — full-run-safety
  • tests/test_backend_env_isolation.py — full-run-safety
  • tests/test_backend_extras.py — full-run-safety
  • tests/test_benchmark.py — full-run-safety
  • tests/test_benchmark_raw_graph.py — full-run-safety
  • tests/test_build.py — impact, full-run-safety
  • tests/test_build_merge_dedup_scope.py — full-run-safety
  • tests/test_build_merge_hyperedges_and_prune.py — full-run-safety
  • tests/test_build_merge_shrink_guard.py — full-run-safety
  • tests/test_builtin_global_type_refs.py — impact, full-run-safety
  • tests/test_cache.py — full-run-safety
  • tests/test_callflow_html.py — full-run-safety
  • tests/test_cargo_introspect.py — full-run-safety
  • tests/test_carried_hyperedge_remap.py — full-run-safety
  • tests/test_case_sensitive_resolution.py — impact, full-run-safety
  • tests/test_charmap_encoding.py — full-run-safety
  • tests/test_chunking.py — full-run-safety
  • tests/test_cjs_module_extension.py — impact, full-run-safety
  • tests/test_claude_cli_backend.py — full-run-safety
  • tests/test_claude_md.py — full-run-safety
  • tests/test_cli_broken_pipe.py — full-run-safety
  • tests/test_cli_export.py — full-run-safety
  • tests/test_cli_help.py — full-run-safety
  • tests/test_cluster.py — full-run-safety
  • tests/test_codebuddy.py — full-run-safety
  • tests/test_community_hub_labels.py — full-run-safety
  • tests/test_community_labels_skill.py — full-run-safety
  • tests/test_confidence.py — full-run-safety
  • tests/test_corrupt_graph_json.py — full-run-safety
  • tests/test_cpp_nested_and_cli.py — impact, full-run-safety
  • tests/test_cpp_objc_cross_file_calls.py — impact, full-run-safety
  • tests/test_cpp_preprocess.py — full-run-safety
  • tests/test_cross_extension_reexport_self_cycle.py — impact, full-run-safety
  • tests/test_cross_language_call_resolution.py — impact, full-run-safety
  • tests/test_cross_repo_external_call_guards.py — impact, full-run-safety
  • tests/test_cross_repo_member_calls.py — impact, full-run-safety
  • tests/test_cross_repo_shared_types.py — full-run-safety
  • tests/test_csharp_call_site_generic_args.py — impact, full-run-safety
  • … and 236 more

non-code file(s) changed (CHANGELOG.md) → running the full suite for safety (a code graph can't see config/fixture/data deps)

changed code file(s) with no mapped test (CHANGELOG.md) — a coverage gap or a missing link — running the full suite rather than only the selected tests

Selection is safe under the controlled-regression assumption; always-run tests + a periodic full run are the backstops. Advisory — it never changes the check verdict.

Formal verification

Could not verify: Could not verify \_extract\_parallel.

The verifier did not have enough to check \_extract\_parallel, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: no capturable inputs from the test suite; property tier: parameter `root` is annotated `Path` — outside the synthesizable primitive/collection set

· 38 more finding(s) on lines outside this diff (see the check run).

ayushcodes10 and others added 3 commits September 17, 2026 21:40
A formal review round on this PR found three real gaps in the regex
this replaces: a guard shaped line sitting inside a triple quoted
string or a docstring example was still read as a real guard, the
exact false positive class this whole check exists to close, just
needing more specific bait text to trigger. And a valid but less
common parenthesized comparison was wrongly rejected as no guard at
all, a regression from the plain substring check this branch started
from. Now parses the caller's source with ast and looks for a real if
statement whose test compares __name__ to the string "__main__" in
either order. The parser never sees string or comment contents as
code at all, and parens are transparent to it, so both gaps close at
once. A source that fails to parse is treated the same as an
unreadable file, matching the existing best effort philosophy here.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Covers the three review reported gaps directly: guard text inside a
triple quoted string, guard text inside a docstring example, and a
parenthesized comparison, plus an unparseable caller falling back to
the existing best effort treatment instead of being escalated.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@ayushcodes10

Copy link
Copy Markdown
Contributor Author

Good catches — fixed both. The regex approach had two real gaps: a guard-shaped line sitting inside a triple-quoted string or docstring example was still read as a real guard (the exact false positive class this check exists to close, just needing more specific bait text), and a valid parenthesized comparison was wrongly rejected as no guard at all.

Switched from a regex to parsing the caller's source with ast and looking for a real if statement whose test compares __name__ to "__main__" in either order. The parser never treats string or comment contents as code at all, and parens are transparent to it, so both gaps close at once. An unparseable source falls back to the same best-effort treatment as an unreadable file. Regression tests cover all three reported gaps plus the fallback.

@graphify-labs graphify-labs Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Graphify reviewed this change.

Worth a look — the grounded gate found no coupling regressions or blocking issues, but 4 advisory finding(s) below merit a look before merge.

Formal verification. No changes could be formally verified in this run.


Graphify review — findings

Prevents an unbounded process spawn on Windows when a guard-less caller script re-invokes extract() at module scope: _extract_parallel now refuses to open a ProcessPoolExecutor unconditionally when already running inside a multiprocessing child, and pre-emptively declines on Windows (warning to stderr) when the caller's __main__ module lacks an if __name__ == "__main__": guard, falling back to sequential extraction in both cases. _caller_main_lacks_guard parses the caller's source and looks for a real if statement whose test compares __name__ to "__main__" via _is_main_guard_test, so a guard-shaped line inside a docstring or comment no longer counts and a parenthesized comparison still does. Unreadable or unparseable caller source is treated as "can't tell" and does not disable the pool.

Worth a look

  • Main-guard detection treats unprotecting guards as safegraphify/extract.py:6426 · Escalate · high
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
  • Windows main-guard check accepts guards that do not protect module-scope extract callsgraphify/extract.py:6427 · Escalate · medium
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
  • Nested main guard bypasses spawn-safety checkgraphify/extract.py:6429 · Escalate · medium
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
  • Windows spawn guard check accepts unrelated nested guardsgraphify/extract.py:6431 · Escalate · medium
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
Analysis details — impact, health, verification

Impact & health

Graphify review

Impact — 2493 functions depend on the 894 functions this change touches.

Health — this change adds coupling hotspots:

  • new: extract() — 645 callers, 45 callees
  • new: _rebuild_code() — 129 callers, 54 callees
  • new: extract_js() — 85 callers, 4 callees
  • new: extract_xaml() — 19 callers, 17 callees
  • new: dispatch_command() — 2 callers, 125 callees
  • new: _get_extractor() — 26 callers, 6 callees
  • new: run_pipeline() — 8 callers, 13 callees
  • new: collect_files() — 17 callers, 6 callees
  • …and 30 more — each is listed as a finding

Verification — 2493 functions in the blast radius were not formally verified this run (proofs are advisory here).

Gate & verification

graphify gate

PASS — objectively clean (no health regressions, tests not run — proofs not run this pass (advisory)). Grounded, not self-assessed.

Advisory (not blocking):

  • verification_scope: 2318 function(s) in the blast radius were not formally verified this run

Test selection

Test selection

286 of 286 test file(s) selected (100%) via static blast radius.

Escalated to a full run for safety — the selection is not trustworthy on its own (see below). CI should run the whole suite.

  • tests/test_affected_cli.py — full-run-safety
  • tests/test_affected_member_seed.py — full-run-safety
  • tests/test_agents_platform.py — full-run-safety
  • tests/test_analyze.py — full-run-safety
  • tests/test_anthropic_custom_endpoint.py — full-run-safety
  • tests/test_antigravity_install.py — full-run-safety
  • tests/test_apm_fallback_version.py — full-run-safety
  • tests/test_architecture_doc.py — full-run-safety
  • tests/test_astro_extraction.py — impact, full-run-safety
  • tests/test_astro_import_ids.py — impact, full-run-safety
  • tests/test_atomic_canvas_export.py — full-run-safety
  • tests/test_atomic_version_stamp.py — full-run-safety
  • tests/test_atomic_writes.py — full-run-safety
  • tests/test_backend_env_isolation.py — full-run-safety
  • tests/test_backend_extras.py — full-run-safety
  • tests/test_benchmark.py — full-run-safety
  • tests/test_benchmark_raw_graph.py — full-run-safety
  • tests/test_build.py — impact, full-run-safety
  • tests/test_build_merge_dedup_scope.py — full-run-safety
  • tests/test_build_merge_hyperedges_and_prune.py — full-run-safety
  • tests/test_build_merge_shrink_guard.py — full-run-safety
  • tests/test_builtin_global_type_refs.py — impact, full-run-safety
  • tests/test_cache.py — full-run-safety
  • tests/test_callflow_html.py — full-run-safety
  • tests/test_cargo_introspect.py — full-run-safety
  • tests/test_carried_hyperedge_remap.py — full-run-safety
  • tests/test_case_sensitive_resolution.py — impact, full-run-safety
  • tests/test_charmap_encoding.py — full-run-safety
  • tests/test_chunking.py — full-run-safety
  • tests/test_cjs_module_extension.py — impact, full-run-safety
  • tests/test_claude_cli_backend.py — full-run-safety
  • tests/test_claude_md.py — full-run-safety
  • tests/test_cli_broken_pipe.py — full-run-safety
  • tests/test_cli_export.py — full-run-safety
  • tests/test_cli_help.py — full-run-safety
  • tests/test_cluster.py — full-run-safety
  • tests/test_codebuddy.py — full-run-safety
  • tests/test_community_hub_labels.py — full-run-safety
  • tests/test_community_labels_skill.py — full-run-safety
  • tests/test_confidence.py — full-run-safety
  • tests/test_corrupt_graph_json.py — full-run-safety
  • tests/test_cpp_nested_and_cli.py — impact, full-run-safety
  • tests/test_cpp_objc_cross_file_calls.py — impact, full-run-safety
  • tests/test_cpp_preprocess.py — full-run-safety
  • tests/test_cross_extension_reexport_self_cycle.py — impact, full-run-safety
  • tests/test_cross_language_call_resolution.py — impact, full-run-safety
  • tests/test_cross_repo_external_call_guards.py — impact, full-run-safety
  • tests/test_cross_repo_member_calls.py — impact, full-run-safety
  • tests/test_cross_repo_shared_types.py — full-run-safety
  • tests/test_csharp_call_site_generic_args.py — impact, full-run-safety
  • … and 236 more

non-code file(s) changed (CHANGELOG.md) → running the full suite for safety (a code graph can't see config/fixture/data deps)

changed code file(s) with no mapped test (CHANGELOG.md) — a coverage gap or a missing link — running the full suite rather than only the selected tests

Selection is safe under the controlled-regression assumption; always-run tests + a periodic full run are the backstops. Advisory — it never changes the check verdict.

Formal verification

Could not verify: Could not verify \_extract\_parallel.

The verifier did not have enough to check \_extract\_parallel, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: no capturable inputs from the test suite; property tier: parameter `root` is annotated `Path` — outside the synthesizable primitive/collection set

· 38 more finding(s) on lines outside this diff (see the check run).

ayushcodes10 and others added 3 commits September 18, 2026 14:13
A fresh review round found that ast.walk() finds a guard anywhere in
the tree, including one nested inside an unrelated function, class,
or dead branch. Such a guard never actually runs at import time and
protects nothing, so a caller whose real module scope code is fully
unguarded could still be treated as safe, letting the exact fork bomb
scenario this check exists to prevent happen anyway. The guard idiom
only has its intended effect as a bare top level statement, so only
the module's direct top level statements are checked now, not every
node anywhere in the source.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Covers a guard nested inside an unrelated function, alongside a
genuinely unguarded module scope extract() call, the exact
combination that let ast.walk() report a guard where there was none.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@ayushcodes10

Copy link
Copy Markdown
Contributor Author

Good catch — fixed. ast.walk() finds a matching if statement anywhere in the tree, including one nested inside an unrelated function, class, or dead branch — which never actually runs at import time and protects nothing. A caller whose real module-scope code was genuinely unguarded could still be reported as safe, letting the exact fork-bomb scenario this check exists to prevent happen anyway.

The guard idiom only has its intended effect as a bare top-level statement, so now only the module's direct top-level statements (tree.body) are checked, not every node anywhere in the source. Regression test covers a guard nested inside an unrelated function alongside a genuinely unguarded module-scope extract() call.

@graphify-labs graphify-labs Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Graphify reviewed this change.

Worth a look — the grounded gate found no coupling regressions or blocking issues, but 2 advisory finding(s) below merit a look before merge.

Formal verification. No changes could be formally verified in this run.


Graphify review — findings

Refuses to open a ProcessPoolExecutor from _extract_parallel when already running inside a multiprocessing child, and on Windows also declines pre-emptively (falling back to sequential extraction with a stderr warning) when the caller's __main__ module lacks an if __name__ == "__main__": guard, preventing the runaway process spawning a guard-less spawn-start caller triggers. Detects the guard by AST-parsing the caller's source via _caller_main_lacks_guard and _is_main_guard_test, matching only a real top-level if comparing __name__ to "__main__" in either operand order — so guard-shaped text in docstrings or comments no longer counts, and parenthesized comparisons do; an unreadable or unparsable source is treated as "can't tell" rather than "missing".

Worth a look

  • Unrelated top-level main guard lets unsafe module-scope extract run in parallelgraphify/extract.py:6431 · Escalate · high
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
  • Parallel extract now silently returns sequentially inside any multiprocessing childgraphify/extract.py:6462 · Escalate · medium
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
Analysis details — impact, health, verification

Impact & health

Graphify review

Impact — 2496 functions depend on the 897 functions this change touches.

Health — this change adds coupling hotspots:

  • new: extract() — 645 callers, 45 callees
  • new: _rebuild_code() — 129 callers, 54 callees
  • new: extract_js() — 85 callers, 4 callees
  • new: extract_xaml() — 19 callers, 17 callees
  • new: dispatch_command() — 2 callers, 125 callees
  • new: _get_extractor() — 26 callers, 6 callees
  • new: run_pipeline() — 8 callers, 13 callees
  • new: collect_files() — 17 callers, 6 callees
  • …and 30 more — each is listed as a finding

Verification — 2496 functions in the blast radius were not formally verified this run (proofs are advisory here).

Gate & verification

graphify gate

PASS — objectively clean (no health regressions, tests not run — proofs not run this pass (advisory)). Grounded, not self-assessed.

Advisory (not blocking):

  • verification_scope: 2321 function(s) in the blast radius were not formally verified this run

Test selection

Test selection

286 of 286 test file(s) selected (100%) via static blast radius.

Escalated to a full run for safety — the selection is not trustworthy on its own (see below). CI should run the whole suite.

  • tests/test_affected_cli.py — full-run-safety
  • tests/test_affected_member_seed.py — full-run-safety
  • tests/test_agents_platform.py — full-run-safety
  • tests/test_analyze.py — full-run-safety
  • tests/test_anthropic_custom_endpoint.py — full-run-safety
  • tests/test_antigravity_install.py — full-run-safety
  • tests/test_apm_fallback_version.py — full-run-safety
  • tests/test_architecture_doc.py — full-run-safety
  • tests/test_astro_extraction.py — impact, full-run-safety
  • tests/test_astro_import_ids.py — impact, full-run-safety
  • tests/test_atomic_canvas_export.py — full-run-safety
  • tests/test_atomic_version_stamp.py — full-run-safety
  • tests/test_atomic_writes.py — full-run-safety
  • tests/test_backend_env_isolation.py — full-run-safety
  • tests/test_backend_extras.py — full-run-safety
  • tests/test_benchmark.py — full-run-safety
  • tests/test_benchmark_raw_graph.py — full-run-safety
  • tests/test_build.py — impact, full-run-safety
  • tests/test_build_merge_dedup_scope.py — full-run-safety
  • tests/test_build_merge_hyperedges_and_prune.py — full-run-safety
  • tests/test_build_merge_shrink_guard.py — full-run-safety
  • tests/test_builtin_global_type_refs.py — impact, full-run-safety
  • tests/test_cache.py — full-run-safety
  • tests/test_callflow_html.py — full-run-safety
  • tests/test_cargo_introspect.py — full-run-safety
  • tests/test_carried_hyperedge_remap.py — full-run-safety
  • tests/test_case_sensitive_resolution.py — impact, full-run-safety
  • tests/test_charmap_encoding.py — full-run-safety
  • tests/test_chunking.py — full-run-safety
  • tests/test_cjs_module_extension.py — impact, full-run-safety
  • tests/test_claude_cli_backend.py — full-run-safety
  • tests/test_claude_md.py — full-run-safety
  • tests/test_cli_broken_pipe.py — full-run-safety
  • tests/test_cli_export.py — full-run-safety
  • tests/test_cli_help.py — full-run-safety
  • tests/test_cluster.py — full-run-safety
  • tests/test_codebuddy.py — full-run-safety
  • tests/test_community_hub_labels.py — full-run-safety
  • tests/test_community_labels_skill.py — full-run-safety
  • tests/test_confidence.py — full-run-safety
  • tests/test_corrupt_graph_json.py — full-run-safety
  • tests/test_cpp_nested_and_cli.py — impact, full-run-safety
  • tests/test_cpp_objc_cross_file_calls.py — impact, full-run-safety
  • tests/test_cpp_preprocess.py — full-run-safety
  • tests/test_cross_extension_reexport_self_cycle.py — impact, full-run-safety
  • tests/test_cross_language_call_resolution.py — impact, full-run-safety
  • tests/test_cross_repo_external_call_guards.py — impact, full-run-safety
  • tests/test_cross_repo_member_calls.py — impact, full-run-safety
  • tests/test_cross_repo_shared_types.py — full-run-safety
  • tests/test_csharp_call_site_generic_args.py — impact, full-run-safety
  • … and 236 more

non-code file(s) changed (CHANGELOG.md) → running the full suite for safety (a code graph can't see config/fixture/data deps)

changed code file(s) with no mapped test (CHANGELOG.md) — a coverage gap or a missing link — running the full suite rather than only the selected tests

Selection is safe under the controlled-regression assumption; always-run tests + a periodic full run are the backstops. Advisory — it never changes the check verdict.

Formal verification

Could not verify: Could not verify \_extract\_parallel.

The verifier did not have enough to check \_extract\_parallel, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: no capturable inputs from the test suite; property tier: parameter `root` is annotated `Path` — outside the synthesizable primitive/collection set

· 38 more finding(s) on lines outside this diff (see the check run).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant